package com.google.code.mochaccino.framework.crypto.scheme;

/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.code.mochaccino.framework.crypto.EncryptionException;
import com.google.code.mochaccino.framework.crypto.cipher.CipherHandler;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import javax.crypto.NoSuchPaddingException;

/** Basic JCE Encryption Scheme. */
public abstract class EncryptionSchemeBase implements EncryptionScheme {

	private String        algorithm;
	private CipherHandler cipherHandler;
	private String        provider;

	public EncryptionSchemeBase( String algorithm, CipherHandler handler ) throws EncryptionException {
		this( algorithm, null, handler );
	}

	public EncryptionSchemeBase( String algorithm, String provider, CipherHandler handler ) throws EncryptionException {
		super();
		Preconditions.checkNotNull( Security.getProvider( provider ), "Unknown security provider" );
		Preconditions.checkNotNull( handler, "handler cannot be null" );
		Preconditions.checkNotNull( algorithm, "Algorithm cannot be null" );
		this.algorithm = algorithm;
		this.provider = provider;
		this.cipherHandler = handler;
		getCipher();
	}

	/** Decrypt */
	@Override
	public final byte[] decrypt( byte[] data ) throws EncryptionException {
		try {
			javax.crypto.Cipher c = getCipher();
			c.init( javax.crypto.Cipher.DECRYPT_MODE, getPrivateKey() );
			return transform( c, data );
		} catch ( InvalidKeyException e ) {
			throw new EncryptionException( e );
		} catch ( GeneralSecurityException e ) {
			throw new EncryptionException( e );
		} catch ( IOException e ) {
			throw new EncryptionException( e );
		}
	}

	/** Encrypt */
	@Override
	public final byte[] encrypt( byte[] data ) throws EncryptionException {
		try {
			javax.crypto.Cipher c = getCipher();
			c.init( javax.crypto.Cipher.ENCRYPT_MODE, getPublicKey() );
			return transform( c, data );
		} catch ( InvalidKeyException e ) {
			throw new EncryptionException( e );
		} catch ( GeneralSecurityException e ) {
			throw new EncryptionException( e );
		} catch ( IOException e ) {
			throw new EncryptionException( e );
		}
	}

	@Override
	public void decrypt( InputStream in, OutputStream out ) throws EncryptionException {
		try {
			javax.crypto.Cipher c = getCipher();
			c.init( javax.crypto.Cipher.DECRYPT_MODE, getPrivateKey() );
			transform( c, in, out );
		} catch ( InvalidKeyException e ) {
			throw new EncryptionException( e );
		} catch ( GeneralSecurityException e ) {
			throw new EncryptionException( e );
		} catch ( IOException e ) {
			throw new EncryptionException( e );
		}
	}

	@Override
	public void encrypt( InputStream in, OutputStream out ) throws EncryptionException {
		try {
			javax.crypto.Cipher c = getCipher();
			c.init( javax.crypto.Cipher.ENCRYPT_MODE, getPrivateKey() );
			transform( c, in, out );
		} catch ( InvalidKeyException e ) {
			throw new EncryptionException( e );
		} catch ( GeneralSecurityException e ) {
			throw new EncryptionException( e );
		} catch ( IOException e ) {
			throw new EncryptionException( e );
		}
	}

	/** Transform Data. */
	private byte[] transform( javax.crypto.Cipher cipher, byte[] data ) throws GeneralSecurityException, IOException {
		return cipherHandler.transform( cipher, data );
	}

	/** Transform Data. */
	private void transform( javax.crypto.Cipher cipher, InputStream data, OutputStream out ) throws GeneralSecurityException, IOException {
		cipherHandler.transform( cipher, data, out );
	}

	/** Return the Private Key */
	protected abstract Key getPrivateKey();

	/** Return the Public Key */
	protected abstract Key getPublicKey();

	/** Get Cipher */
	protected final javax.crypto.Cipher getCipher() throws EncryptionException {
		try {
			if ( getProvider() != null ) {
				return javax.crypto.Cipher.getInstance( getAlgorithm(), getProvider() );
			}
			return javax.crypto.Cipher.getInstance( getAlgorithm() );
		} catch ( NoSuchAlgorithmException e ) {
			throw new EncryptionException( e );
		} catch ( NoSuchProviderException e ) {
			throw new EncryptionException( e );
		} catch ( NoSuchPaddingException e ) {
			throw new EncryptionException( e );
		}
	}

	/** Get Algorithm */
	public String getAlgorithm() {
		return algorithm;
	}

	/** Return the Provider */
	public String getProvider() {
		return provider;
	}
}
